/*
 * contrib/xml2/xpath.c
 *
 * DOM基础解析器（libxml）的解析器接口，而不是
 * 基于流的SAX类型解析器
 */
#include "postgres.h"

#include "access/htup_details.h"
#include "executor/spi.h"
#include "fmgr.h"
#include "funcapi.h"
#include "lib/stringinfo.h"
#include "miscadmin.h"
#include "utils/builtins.h"
#include "utils/xml.h"

/* libxml包含 */

#include <libxml/xpath.h>
#include <libxml/tree.h>
#include <libxml/xmlmemory.h>
#include <libxml/xmlerror.h>
#include <libxml/parserInternals.h>

PG_MODULE_MAGIC;

/* 供xslt_proc.c使用的导出 */

PgXmlErrorContext *pgxml_parser_init(PgXmlStrictness fc_strictness);

/* pgxml_xpath()的工作区 */

typedef struct
{
	xmlDocPtr	doctree;
	xmlXPathContextPtr ctxt;
	xmlXPathObjectPtr res;
} xpath_workspace;

/* 局部声明 */

static xmlChar *fc_pgxmlNodeSetToText(xmlNodeSetPtr fc_nodeset,
								   xmlChar *fc_toptagname, xmlChar *fc_septagname,
								   xmlChar *fc_plainsep);

static text *fc_pgxml_result_to_text(xmlXPathObjectPtr fc_res, xmlChar *fc_toptag,
								  xmlChar *fc_septag, xmlChar *fc_plainsep);

static xmlChar *fc_pgxml_texttoxmlchar(text *fc_textstring);

static xmlXPathObjectPtr fc_pgxml_xpath(text *fc_document, xmlChar *fc_xpath,
									 xpath_workspace *fc_workspace);

static void fc_cleanup_workspace(xpath_workspace *fc_workspace);


/*
 * 为xml解析初始化。
 *
 * 与底层的pg_xml_init函数一样，对这个函数的调用必须后跟
 * 一个确保调用pg_xml_done的PG_TRY块。
 */
PgXmlErrorContext *
pgxml_parser_init(PgXmlStrictness fc_strictness)
{
	PgXmlErrorContext *fc_xmlerrcxt;

	/* 设置错误处理（我们共享核心的错误处理程序） */
	fc_xmlerrcxt = pg_xml_init(fc_strictness);

	/* 注意：我们假设以下调用不会引发elog */

	/* 初始化libxml */
	xmlInitParser();

	return fc_xmlerrcxt;
}


/* 将特殊字符（<, >, &, "和\r）编码为XML实体 */

PG_FUNCTION_INFO_V1(xml_encode_special_chars);

Datum xml_encode_special_chars(PG_FUNCTION_ARGS)
{
	text	   *fc_tin = PG_GETARG_TEXT_PP(0);
	text	   *fc_tout;
	xmlChar    *fc_ts,
			   *fc_tt;

	fc_ts = fc_pgxml_texttoxmlchar(fc_tin);

	fc_tt = xmlEncodeSpecialChars(NULL, fc_ts);

	pfree(fc_ts);

	fc_tout = cstring_to_text((char *) fc_tt);

	xmlFree(fc_tt);

	PG_RETURN_TEXT_P(fc_tout);
}

/*
 * 函数将节点集转换为文本表示
 *
 * 遍历集合中的每个节点，并调用xmlNodeDump将其写入
 * xmlBuffer，从中返回一个xmlChar *字符串。
 *
 * 每个表示都被包围在<tagname> ... </tagname>中
 *
 * plainsep是普通（非标签）分隔符 - 如果使用，则节点会被
 * 转换为字符串作为输出方法
 */
static xmlChar * fc_pgxmlNodeSetToText(xmlNodeSetPtr fc_nodeset,
				   xmlChar *fc_toptagname,
				   xmlChar *fc_septagname,
				   xmlChar *fc_plainsep)
{
	xmlBufferPtr fc_buf;
	xmlChar    *fc_result;
	int			fc_i;

	fc_buf = xmlBufferCreate();

	if ((fc_toptagname != NULL) && (xmlStrlen(fc_toptagname) > 0))
	{
		xmlBufferWriteChar(fc_buf, "<");
		xmlBufferWriteCHAR(fc_buf, fc_toptagname);
		xmlBufferWriteChar(fc_buf, ">");
	}
	if (fc_nodeset != NULL)
	{
		for (fc_i = 0; fc_i < fc_nodeset->nodeNr; fc_i++)
		{
			if (fc_plainsep != NULL)
			{
				xmlBufferWriteCHAR(fc_buf,
								   xmlXPathCastNodeToString(fc_nodeset->nodeTab[fc_i]));

				/* 如果这不是最后一个条目，写入普通分隔符。 */
				if (fc_i < (fc_nodeset->nodeNr) - 1)
					xmlBufferWriteChar(fc_buf, (char *) fc_plainsep);
			}
			else
			{
				if ((fc_septagname != NULL) && (xmlStrlen(fc_septagname) > 0))
				{
					xmlBufferWriteChar(fc_buf, "<");
					xmlBufferWriteCHAR(fc_buf, fc_septagname);
					xmlBufferWriteChar(fc_buf, ">");
				}
				xmlNodeDump(fc_buf,
							fc_nodeset->nodeTab[fc_i]->doc,
							fc_nodeset->nodeTab[fc_i],
							1, 0);

				if ((fc_septagname != NULL) && (xmlStrlen(fc_septagname) > 0))
				{
					xmlBufferWriteChar(fc_buf, "</");
					xmlBufferWriteCHAR(fc_buf, fc_septagname);
					xmlBufferWriteChar(fc_buf, ">");
				}
			}
		}
	}

	if ((fc_toptagname != NULL) && (xmlStrlen(fc_toptagname) > 0))
	{
		xmlBufferWriteChar(fc_buf, "</");
		xmlBufferWriteCHAR(fc_buf, fc_toptagname);
		xmlBufferWriteChar(fc_buf, ">");
	}
	fc_result = xmlStrdup(fc_buf->content);
	xmlBufferFree(fc_buf);
	return fc_result;
}


/* 将PostgreSQL的“varlena” -即一个可变长度参数
 * 转换为libxml2表示
 */
static xmlChar * fc_pgxml_texttoxmlchar(text *fc_textstring)
{
	return (xmlChar *) text_to_cstring(fc_textstring);
}

/* 公开可见的XPath函数 */

/*
 * 这是一个“原始”的xpath函数。检查它是否正确返回子元素
 */
PG_FUNCTION_INFO_V1(xpath_nodeset);

Datum xpath_nodeset(PG_FUNCTION_ARGS)
{
	text	   *fc_document = PG_GETARG_TEXT_PP(0);
	text	   *fc_xpathsupp = PG_GETARG_TEXT_PP(1);	/* XPath表达式 */
	xmlChar    *fc_toptag = fc_pgxml_texttoxmlchar(PG_GETARG_TEXT_PP(2));
	xmlChar    *fc_septag = fc_pgxml_texttoxmlchar(PG_GETARG_TEXT_PP(3));
	xmlChar    *fc_xpath;
	text	   *fc_xpres;
	xmlXPathObjectPtr fc_res;
	xpath_workspace fc_workspace;

	fc_xpath = fc_pgxml_texttoxmlchar(fc_xpathsupp);

	fc_res = fc_pgxml_xpath(fc_document, fc_xpath, &fc_workspace);

	fc_xpres = fc_pgxml_result_to_text(fc_res, fc_toptag, fc_septag, NULL);

	fc_cleanup_workspace(&fc_workspace);

	pfree(fc_xpath);

	if (fc_xpres == NULL)
		PG_RETURN_NULL();
	PG_RETURN_TEXT_P(fc_xpres);
}

/*
 * 以下函数几乎相同，但返回一个列表中的元素。
 */
PG_FUNCTION_INFO_V1(xpath_list);

Datum xpath_list(PG_FUNCTION_ARGS)
{
	text	   *fc_document = PG_GETARG_TEXT_PP(0);
	text	   *fc_xpathsupp = PG_GETARG_TEXT_PP(1);	/* XPath表达式 */
	xmlChar    *fc_plainsep = fc_pgxml_texttoxmlchar(PG_GETARG_TEXT_PP(2));
	xmlChar    *fc_xpath;
	text	   *fc_xpres;
	xmlXPathObjectPtr fc_res;
	xpath_workspace fc_workspace;

	fc_xpath = fc_pgxml_texttoxmlchar(fc_xpathsupp);

	fc_res = fc_pgxml_xpath(fc_document, fc_xpath, &fc_workspace);

	fc_xpres = fc_pgxml_result_to_text(fc_res, NULL, NULL, fc_plainsep);

	fc_cleanup_workspace(&fc_workspace);

	pfree(fc_xpath);

	if (fc_xpres == NULL)
		PG_RETURN_NULL();
	PG_RETURN_TEXT_P(fc_xpres);
}


PG_FUNCTION_INFO_V1(xpath_string);

Datum xpath_string(PG_FUNCTION_ARGS)
{
	text	   *fc_document = PG_GETARG_TEXT_PP(0);
	text	   *fc_xpathsupp = PG_GETARG_TEXT_PP(1);	/* XPath表达式 */
	xmlChar    *fc_xpath;
	int32		fc_pathsize;
	text	   *fc_xpres;
	xmlXPathObjectPtr fc_res;
	xpath_workspace fc_workspace;

	fc_pathsize = VARSIZE_ANY_EXHDR(fc_xpathsupp);

	/*
	 * 我们将提供的路径封装为“string()” = 8个字符 + 1个用于NUL
	 * 在结尾
	 */
	/* 我们可以尝试使用libxml函数转换为字符串吗？ */

	fc_xpath = (xmlChar *) palloc(fc_pathsize + 9);
	memcpy((char *) fc_xpath, "string(", 7);
	memcpy((char *) (fc_xpath + 7), VARDATA_ANY(fc_xpathsupp), fc_pathsize);
	fc_xpath[fc_pathsize + 7] = ')';
	fc_xpath[fc_pathsize + 8] = '\0';

	fc_res = fc_pgxml_xpath(fc_document, fc_xpath, &fc_workspace);

	fc_xpres = fc_pgxml_result_to_text(fc_res, NULL, NULL, NULL);

	fc_cleanup_workspace(&fc_workspace);

	pfree(fc_xpath);

	if (fc_xpres == NULL)
		PG_RETURN_NULL();
	PG_RETURN_TEXT_P(fc_xpres);
}


PG_FUNCTION_INFO_V1(xpath_number);

Datum xpath_number(PG_FUNCTION_ARGS)
{
	text	   *fc_document = PG_GETARG_TEXT_PP(0);
	text	   *fc_xpathsupp = PG_GETARG_TEXT_PP(1);	/* XPath表达式 */
	xmlChar    *fc_xpath;
	float4		fc_fRes;
	xmlXPathObjectPtr fc_res;
	xpath_workspace fc_workspace;

	fc_xpath = fc_pgxml_texttoxmlchar(fc_xpathsupp);

	fc_res = fc_pgxml_xpath(fc_document, fc_xpath, &fc_workspace);

	pfree(fc_xpath);

	if (fc_res == NULL)
		PG_RETURN_NULL();

	fc_fRes = xmlXPathCastToNumber(fc_res);

	fc_cleanup_workspace(&fc_workspace);

	if (xmlXPathIsNaN(fc_fRes))
		PG_RETURN_NULL();

	PG_RETURN_FLOAT4(fc_fRes);
}


PG_FUNCTION_INFO_V1(xpath_bool);

Datum xpath_bool(PG_FUNCTION_ARGS)
{
	text	   *fc_document = PG_GETARG_TEXT_PP(0);
	text	   *fc_xpathsupp = PG_GETARG_TEXT_PP(1);	/* XPath表达式 */
	xmlChar    *fc_xpath;
	int			fc_bRes;
	xmlXPathObjectPtr fc_res;
	xpath_workspace fc_workspace;

	fc_xpath = fc_pgxml_texttoxmlchar(fc_xpathsupp);

	fc_res = fc_pgxml_xpath(fc_document, fc_xpath, &fc_workspace);

	pfree(fc_xpath);

	if (fc_res == NULL)
		PG_RETURN_BOOL(false);

	fc_bRes = xmlXPathCastToBoolean(fc_res);

	fc_cleanup_workspace(&fc_workspace);

	PG_RETURN_BOOL(fc_bRes);
}



/* 评估XPath查询的核心函数 */

static xmlXPathObjectPtr
fc_pgxml_xpath(text *fc_document, xmlChar *fc_xpath, xpath_workspace *fc_workspace)
{
	int32		fc_docsize = VARSIZE_ANY_EXHDR(fc_document);
	PgXmlErrorContext *fc_xmlerrcxt;
	xmlXPathCompExprPtr fc_comppath;

	fc_workspace->doctree = NULL;
	fc_workspace->ctxt = NULL;
	fc_workspace->res = NULL;

	fc_xmlerrcxt = pgxml_parser_init(PG_XML_STRICTNESS_LEGACY);

	PG_TRY();
	{
		fc_workspace->doctree = xmlReadMemory((char *) VARDATA_ANY(fc_document),
										   fc_docsize, NULL, NULL,
										   XML_PARSE_NOENT);
		if (fc_workspace->doctree != NULL)
		{
			fc_workspace->ctxt = xmlXPathNewContext(fc_workspace->doctree);
			fc_workspace->ctxt->node = xmlDocGetRootElement(fc_workspace->doctree);

			/* 编译路径 */
			fc_comppath = xmlXPathCtxtCompile(fc_workspace->ctxt, fc_xpath);
			if (fc_comppath == NULL)
				xml_ereport(fc_xmlerrcxt, ERROR, ERRCODE_EXTERNAL_ROUTINE_EXCEPTION,
							"XPath Syntax Error");

			/* 现在评估路径表达式。 */
			fc_workspace->res = xmlXPathCompiledEval(fc_comppath, fc_workspace->ctxt);

			xmlXPathFreeCompExpr(fc_comppath);
		}
	}
	PG_CATCH();
	{
		fc_cleanup_workspace(fc_workspace);

		pg_xml_done(fc_xmlerrcxt, true);

		PG_RE_THROW();
	}
	PG_END_TRY();

	if (fc_workspace->res == NULL)
		fc_cleanup_workspace(fc_workspace);

	pg_xml_done(fc_xmlerrcxt, false);

	return fc_workspace->res;
}

/* 处理pgxml_xpath()结果后进行清理 */
static void fc_cleanup_workspace(xpath_workspace *fc_workspace)
{
	if (fc_workspace->res)
		xmlXPathFreeObject(fc_workspace->res);
	fc_workspace->res = NULL;
	if (fc_workspace->ctxt)
		xmlXPathFreeContext(fc_workspace->ctxt);
	fc_workspace->ctxt = NULL;
	if (fc_workspace->doctree)
		xmlFreeDoc(fc_workspace->doctree);
	fc_workspace->doctree = NULL;
}

static text * fc_pgxml_result_to_text(xmlXPathObjectPtr fc_rc_res,
					 xmlChar *fc_toptag,
					 xmlChar *fc_septag,
					 xmlChar *fc_plainsep)
{
	xmlChar    *fc_xpresstr;
	text	   *fc_xpres;

	if (fc_rc_res == NULL)
		return NULL;

	switch (fc_rc_res->type)
	{
		case XPATH_NODESET:
			fc_xpresstr = fc_pgxmlNodeSetToText(fc_rc_res->nodesetval,
										  fc_toptag,
										  fc_septag, fc_plainsep);
			break;

		case XPATH_STRING:
			fc_xpresstr = xmlStrdup(fc_rc_res->stringval);
			break;

		default:
			elog(NOTICE, "unsupported XQuery result: %d", fc_rc_res->type);
			fc_xpresstr = xmlStrdup((const xmlChar *) "<unsupported/>");
	}

	/* 现在将此结果转换回文本 */
	fc_xpres = cstring_to_text((char *) fc_xpresstr);

	/* 释放各种存储 */
	xmlFree(fc_xpresstr);

	return fc_xpres;
}

/*
 * xpath_table是一个表函数。它需要一些整理（其他
 * 函数也是如此！）
 */
PG_FUNCTION_INFO_V1(xpath_table);

Datum xpath_table(PG_FUNCTION_ARGS)
{
	/* 函数参数 */
	char	   *fc_pkeyfield = text_to_cstring(PG_GETARG_TEXT_PP(0));
	char	   *fc_xmlfield = text_to_cstring(PG_GETARG_TEXT_PP(1));
	char	   *fc_relname = text_to_cstring(PG_GETARG_TEXT_PP(2));
	char	   *fc_xpathset = text_to_cstring(PG_GETARG_TEXT_PP(3));
	char	   *fc_condition = text_to_cstring(PG_GETARG_TEXT_PP(4));

	/* SPI（输入元组）支持 */
	SPITupleTable *fc_tuptable;
	HeapTuple	fc_spi_tuple;
	TupleDesc	fc_spi_tupdesc;


	ReturnSetInfo *fc_rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
	AttInMetadata *fc_attinmeta;

	char	  **fc_values;
	xmlChar   **fc_xpaths;
	char	   *fc_pos;
	const char *fc_pathsep = "|";

	int			fc_numpaths;
	int			fc_ret;
	uint64		fc_proc;
	int			fc_j;
	int			fc_rownr;			/* 从一个原始文档发出多行 */
	bool		fc_had_values;		/* 用于确定节点集结果的结束 */
	StringInfoData fc_query_buf;
	PgXmlErrorContext *fc_xmlerrcxt;
	volatile xmlDocPtr fc_doctree = NULL;

	InitMaterializedSRF(fcinfo, MAT_SRF_USE_EXPECTED_DESC);

	/* 必须至少有一个输出列（用于pkey） */
	if (fc_rsinfo->setDesc->natts < 1)
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg("xpath_table must have at least one output column")));

	/*
	 * 目前我们假设返回的属性对于指定的XPath是有意义的
	 * （即我们信任调用者）。如果他们搞错了这并不是致命的 -
	 * 如果路径结果无法转换为正确的二进制
	 * 表示，列类型的输入函数将会引发错误。
	 */

	fc_attinmeta = TupleDescGetAttInMetadata(fc_rsinfo->setDesc);

	fc_values = (char **) palloc(fc_rsinfo->setDesc->natts * sizeof(char *));
	fc_xpaths = (xmlChar **) palloc(fc_rsinfo->setDesc->natts * sizeof(xmlChar *));

	/*
	 * 分割XPath。xpathset是一个可写的CString。
	 *
	 * 注意，一旦我们完成了tupdesc所需的所有部分，我们就停止分割
	 */
	fc_numpaths = 0;
	fc_pos = fc_xpathset;
	while (fc_numpaths < (fc_rsinfo->setDesc->natts - 1))
	{
		fc_xpaths[fc_numpaths++] = (xmlChar *) fc_pos;
		fc_pos = strstr(fc_pos, fc_pathsep);
		if (fc_pos != NULL)
		{
			*fc_pos = '\0';
			fc_pos++;
		}
		else
			break;
	}

	/* 现在构建查询 */
	initStringInfo(&fc_query_buf);

	/* 构建初始 SQL 语句 */
	appendStringInfo(&fc_query_buf, "SELECT %s, %s FROM %s WHERE %s",
					 fc_pkeyfield,
					 fc_xmlfield,
					 fc_relname,
					 fc_condition);

	if ((fc_ret = SPI_connect()) < 0)
		elog(ERROR, "xpath_table: SPI_connect returned %d", fc_ret);

	if ((fc_ret = SPI_exec(fc_query_buf.data, 0)) != SPI_OK_SELECT)
		elog(ERROR, "xpath_table: SPI execution failed for query %s",
			 fc_query_buf.data);

	fc_proc = SPI_processed;
	fc_tuptable = SPI_tuptable;
	fc_spi_tupdesc = fc_tuptable->tupdesc;

	/*
	 * 检查SPI返回的结果是否正确。如果你在函数参数中放入一个逗号，当SPI查询返回
	 * 例如3列时，这将捕获到它。
	 */
	if (fc_spi_tupdesc->natts != 2)
	{
		ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						errmsg("expression returning multiple columns is not valid in parameter list"),
						errdetail("Expected two columns in SPI result, got %d.", fc_spi_tupdesc->natts)));
	}

	/*
	 * 设置解析器。这应该在我们完成查询评估后进行，以防它调用以不同方式设置libxml的函数。
	 */
	fc_xmlerrcxt = pgxml_parser_init(PG_XML_STRICTNESS_LEGACY);

	PG_TRY();
	{
		/* 对于从SPI返回的每一行，即文档 */
		uint64		fc_i;

		for (fc_i = 0; fc_i < fc_proc; fc_i++)
		{
			char	   *fc_pkey;
			char	   *fc_xmldoc;
			xmlXPathContextPtr fc_ctxt;
			xmlXPathObjectPtr fc_res;
			xmlChar    *fc_resstr;
			xmlXPathCompExprPtr fc_comppath;
			HeapTuple	fc_ret_tuple;

			/* 将行数据提取为C字符串 */
			fc_spi_tuple = fc_tuptable->vals[fc_i];
			fc_pkey = SPI_getvalue(fc_spi_tuple, fc_spi_tupdesc, 1);
			fc_xmldoc = SPI_getvalue(fc_spi_tuple, fc_spi_tupdesc, 2);

			/*
			 * 清除值数组，以便格式不正确的文档在所有列中返回NULL。请注意，这也意味着
			 * 多余的列将为NULL。
			 */
			for (fc_j = 0; fc_j < fc_rsinfo->setDesc->natts; fc_j++)
				fc_values[fc_j] = NULL;

			/* 插入主键 */
			fc_values[0] = fc_pkey;

			/* 解析文档 */
			if (fc_xmldoc)
				fc_doctree = xmlReadMemory(fc_xmldoc, strlen(fc_xmldoc),
										NULL, NULL,
										XML_PARSE_NOENT);
			else				/* 将NULL视为格式不正确 */
				fc_doctree = NULL;

			if (fc_doctree == NULL)
			{
				/* 格式不正确，因此输出全NULL元组 */
				fc_ret_tuple = BuildTupleFromCStrings(fc_attinmeta, fc_values);
				tuplestore_puttuple(fc_rsinfo->setResult, fc_ret_tuple);
				heap_freetuple(fc_ret_tuple);
			}
			else
			{
				/* 这里是新的循环 - 我们必须处理节点集结果 */
				fc_rownr = 0;

				do
				{
					/* 现在评估XPath集合。 */
					fc_had_values = false;
					for (fc_j = 0; fc_j < fc_numpaths; fc_j++)
					{
						fc_ctxt = xmlXPathNewContext(fc_doctree);
						fc_ctxt->node = xmlDocGetRootElement(fc_doctree);

						/* 编译路径 */
						fc_comppath = xmlXPathCtxtCompile(fc_ctxt, fc_xpaths[fc_j]);
						if (fc_comppath == NULL)
							xml_ereport(fc_xmlerrcxt, ERROR,
										ERRCODE_EXTERNAL_ROUTINE_EXCEPTION,
										"XPath Syntax Error");

						/* 现在评估路径表达式。 */
						fc_res = xmlXPathCompiledEval(fc_comppath, fc_ctxt);
						xmlXPathFreeCompExpr(fc_comppath);

						if (fc_res != NULL)
						{
							switch (fc_res->type)
							{
								case XPATH_NODESET:
									/* 我们查看这个节点集是否有足够的节点 */
									if (fc_res->nodesetval != NULL &&
										fc_rownr < fc_res->nodesetval->nodeNr)
									{
										fc_resstr = xmlXPathCastNodeToString(fc_res->nodesetval->nodeTab[fc_rownr]);
										fc_had_values = true;
									}
									else
										fc_resstr = NULL;

									break;

								case XPATH_STRING:
									fc_resstr = xmlStrdup(fc_res->stringval);
									break;

								default:
									elog(NOTICE, "unsupported XQuery result: %d", fc_res->type);
									fc_resstr = xmlStrdup((const xmlChar *) "<unsupported/>");
							}

							/*
							 * 将其插入结果元组的适当列中。
							 */
							fc_values[fc_j + 1] = (char *) fc_resstr;
						}
						xmlXPathFreeContext(fc_ctxt);
					}

					/* 现在将元组添加到输出中（如果有的话）。 */
					if (fc_had_values)
					{
						fc_ret_tuple = BuildTupleFromCStrings(fc_attinmeta, fc_values);
						tuplestore_puttuple(fc_rsinfo->setResult, fc_ret_tuple);
						heap_freetuple(fc_ret_tuple);
					}

					fc_rownr++;
				} while (fc_had_values);
			}

			if (fc_doctree != NULL)
				xmlFreeDoc(fc_doctree);
			fc_doctree = NULL;

			if (fc_pkey)
				pfree(fc_pkey);
			if (fc_xmldoc)
				pfree(fc_xmldoc);
		}
	}
	PG_CATCH();
	{
		if (fc_doctree != NULL)
			xmlFreeDoc(fc_doctree);

		pg_xml_done(fc_xmlerrcxt, true);

		PG_RE_THROW();
	}
	PG_END_TRY();

	if (fc_doctree != NULL)
		xmlFreeDoc(fc_doctree);

	pg_xml_done(fc_xmlerrcxt, false);

	SPI_finish();

	/*
	 * SFRM_Materialize模式期望我们返回一个NULL Datum。实际
	 * 元组在我们的元组存储中，通过rsinfo->setResult返回。
	 * rsinfo->setDesc设置为我们实际用于构建元组的元组描述，
	 * 这样调用者可以验证我们所做的是否符合预期。
	 */
	return (Datum) 0;
}
